using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Text.RegularExpressions;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using Rest;
using Rest.Transport;

namespace Coriander.Google.Services.Authentication
{
    /// <summary>
    /// Class for producing an X-GOOGLE-TOKEN. 
    /// </summary>
    /// <remarks>
    /// This token can be used to log in to services like Google Talk, which supports
    /// X-GOOGLE-TOKEN login as well as SASL PLAIN over SSL.
    /// </remarks> 
    public class ClientAuth
    {
        /// <summary>
        /// The pattern for authentication responses
        /// </summary>
        private static readonly Regex clientAuthRegex = new Regex(
            @"^SID=(?<sid>[A-Za-z0-9_-]+)\r\n" +
            @"LSID=(?<lsid>[A-Za-z0-9_-]+)\r\n$",
            RegexOptions.Compiled);

        private const String ClientAuthServiceUrl =
            "https://www.google.com:443/accounts/ClientAuth";

        private const String IssueAuthTokenServiceUrl =
            "https://www.google.com:443/accounts/IssueAuthToken";

        /// <summary>
        /// Authenticates the supplied credential against Google accounts.
        /// </summary>
        /// <remarks>
        /// See: http://dystopics.dump.be/2006/02/04/the-mysteries-of-x-google-token-and-why-it-matters/
        /// </remarks>
        /// <param name="credential"></param>
        /// <param name="service">The service to be authenticated against</param>
        /// <returns></returns>
        public static XGoogleToken Authenticate(
            NetworkCredential credential,
            String service
        )
        {
            RestCommand command = new RestCommand();
            command.ServiceUri = new System.Uri(ClientAuthServiceUrl);
            command.Transport = new PostRestTransport();

            command.Parameters.Add("Email"              , credential.UserName);
            command.Parameters.Add("Passwd"             , credential.Password);
            command.Parameters.Add("PersistentCookie"   , "false");

            String responseText, sid, lsid;

            sid = lsid = String.Empty;

            using (StreamReader reader = command.ExecuteStreamReader())
            {
                using (StringWriter writer = new StringWriter())
                {
                    while (reader.Peek() > 0)
                    {
                        writer.WriteLine(reader.ReadLine());
                    }

                    responseText = writer.GetStringBuilder().ToString();
                }
            }

            Match match;

            if ((match = clientAuthRegex.Match(responseText)).Success)
            {
                if (match.Captures.Count > 0 && match.Groups.Count > 2)
                {
                    sid = match.Groups["sid"].Value;
                    lsid = match.Groups["lsid"].Value;
                }
            }

            if (match.Success)
            {
                //
                // Issue the token
                //
                command.ServiceUri = new System.Uri(IssueAuthTokenServiceUrl);
                command.Parameters.Clear();
                command.Parameters.Add("SID"        ,   sid);
                command.Parameters.Add("LSID"       , lsid);
                command.Parameters.Add("service"    , service);
                command.Parameters.Add("Session"    , "true");

                String rawToken;

                using (StreamReader reader = command.ExecuteStreamReader())
                {
                    rawToken = reader.ReadToEnd();
                }

                return new XGoogleToken(credential.UserName, rawToken);
            }
            else
            {
                return XGoogleToken.Empty;
            }
        }
    }
}
